// node generator_demo.js
// npx babel-node generator_demo.js

let _ = '';
const {log} = console;

function* helloWorldGenerator() {
    yield 'hello';
    yield 'world';
    return 'ending';
}

let test1 = function(){
    let hw = helloWorldGenerator();

    _ = hw.next();
    log(_);

    _ = hw.next();
    log(_);

    _ = hw.next();
    log(_);

    _ = hw.next();
    log(_);
};
// test1();

// Generator 函数可以不用yield表达式，这时就变成了一个单纯的暂缓执行函数。
let test2 = function(){
    let f = function*(){
        log('执行了!');
    }
    let generator = f();
    setTimeout(function(){
        generator.next();
    }, 2000);
};
// test2();

// 另外需要注意，yield表达式只能用在 Generator 函数里面，用在其他地方都会报错。
let test3 = function(){
    // 报错
    // (function(){
    //     yield 1;
    // })();
};
// test3();

// 由于 Generator 函数就是遍历器生成函数，因此可以把 Generator 赋值给对象的Symbol.iterator属性，从而使得该对象具有 Iterator 接口。

let test4 = function(){
    let myIterable = {};
    myIterable[Symbol.iterator] = function*(){
        yield 1;
        yield 2;
        yield 3;
    };

    _ = [...myIterable];
    log(_);
};
// test4();


// Generator 函数执行后，返回一个遍历器对象。该对象本身也具有Symbol.iterator属性，执行后返回自身。
let test5 = function(){
    let gen = function*(){
        // some code
    };

    let g = gen();

    _ = g[Symbol.iterator]() === g;
    log(_);
};
// test5();
// 上面代码中，gen是一个 Generator 函数，调用它会生成一个遍历器对象g。它的Symbol.iterator属性，也是一个遍历器对象生成函数，执行后返回它自己。

// next 方法的参数
let test6 = function(){
    let f = function*(){
        for (let i = 0; true; i++) {
            let reset = yield i;
            if (reset) { i = -1; }
        }
    };

    let g = f();

    _ = g.next();
    log(_);

    _ = g.next();
    log(_);

    _ = g.next(true);
    log(_);
};
// test6();

let test7 = function(){
    let foo = function*(x){
        let y = 2 * (yield (x + 1));
        let z = yield (y / 3);
        return (x + y + z);
    };

    let a = foo(5);
    _ = a.next();
    log(_);
    _ = a.next();
    log(_);
    _ = a.next();
    log(_);

    log('-'.repeat(6));

    // 如果向next方法提供参数，返回结果就完全不一样了。上面代码第一次调用b的next方法时，返回x+1的值6；第二次调用next方法，将上一次yield表达式的值设为12，因此y等于24，返回y / 3的值8；第三次调用next方法，将上一次yield表达式的值设为13，因此z等于13，这时x等于5，y等于24，所以return语句的值等于42。
    let b = foo(5);
    _ = b.next();
    log(_);
    _ = b.next(12);
    log(_);
    _ = b.next(13);
    log(_);
};
// test7();

// 从语义上讲，第一个next方法用来启动遍历器对象，所以不用带有参数。
let test8 = function(){
    let dataConsumer = function*(){
        log('Started');
        log(`1. ${yield}`);
        log(`2. ${yield}`);
        return 'result';
    };

    let genObj = dataConsumer();
    genObj.next();
    genObj.next('a');
    genObj.next('b');
    _ = genObj.next();
    log(_);
};
// test8();

// 如果想要第一次调用next方法时，就能够输入值，可以在 Generator 函数外面再包一层。
let test9 = function(){
    let wrapper = function(generatorFunction){
        return function(...args){
            let generatorObject = generatorFunction(...args);
            generatorObject.next();
            return generatorObject;
        };
    };

    const wrapped = wrapper(function*(){
        log(`First input: ${yield}`);
        log(`Second input: ${yield}`);
        // return 'DONE';
    });

    let gen = wrapped();
    gen.next('hello');
    gen.next('world');
    _ = gen.next('done');
    log(_)
};
// 上面代码中，Generator 函数如果不用wrapper先包一层，是无法第一次调用next方法，就输入参数的。
// test9();

// for...of循环可以自动遍历 Generator 函数运行时生成的Iterator对象，且此时不再需要调用next方法。
let test10 = function(){
    let foo = function*(){
        yield 1;
        yield 2;
        yield 3;
        yield 4;
        yield 5;
        return 6;
    };

    for (let v of foo()) {
        log(v);
    }
};
// test10();

let test11 = function(){
    let fibonacci = function*(){
        let [prev, curr] = [0, 1];
        for (;;) {
            yield curr;
            [prev, curr] = [curr, prev + curr];
        }
    };

    for (let n of fibonacci()) {
        if (n > 1000) break;
        log(n);
    }
};
// test11();

// 利用for...of循环，可以写出遍历任意对象（object）的方法。原生的 JavaScript 对象没有遍历接口，无法使用for...of循环，通过 Generator 函数为它加上这个接口，就可以用了。

let test12 = function(){
    let objectEntries = function*(obj){
        let propKeys = Reflect.ownKeys(obj);

        for(let key of propKeys) {
            yield [key, obj[key]];
        }
    };

    let jane = { first: 'Jane', last: 'Doe' };

    for (let [key, val] of objectEntries(jane)) {
        log(`${key}: ${val}`);
    }
};
// test12();

// 加上遍历器接口的另一种写法是，将 Generator 函数加到对象的Symbol.iterator属性上面。

let test13 = function(){
    let objectEntries = function*(){
        let propKeys = Object.keys(this);

        for(let key of propKeys) {
            yield [key, this[key]];
        }
    };

    let jane = { first: 'Jane', last: 'Doe' };

    jane[Symbol.iterator] = objectEntries;

    for (let [key, val] of jane) {
        log(`${key}: ${val}`);
    }
};
// test13();

let test14 = function(){
    let numbers = function*(){
        yield 1;
        yield 2;
        return 3;
        yield 4;
    };

    _ = [...numbers()];
    log(_);

    _ = Array.from(numbers());
    log(_);

    let [x, y] = numbers();
    log(x, y);

    for(let n of numbers()) {
        log(n);
    }
};
// test14();

// throw
let test15 = function(){
    let g = function*(){
        try{
            yield;
        }catch(e){
            log('内部捕获', e);
        }
    };

    let i = g();
    i.next();

    try{
        i.throw('a');
        i.throw('b');
    }catch(e){
        log('外部捕获', e);
    }
};
// test15();













